#ifndef _EVENT_H
#define _EVENT_H

namespace math 
{
namespace ui 
{

class EventHandler {
public:
	EventHandler() {}
	virtual ~ EventHandler() {}
	virtual void OnEvent(SDL_Event* Event);
	virtual void OnInputFocus(){}
	virtual void OnInputBlur(){}
	virtual void OnKeyDown(SDLKey sym, SDLMod mod, Uint16 unicode){}
	virtual void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode){}
	virtual void OnMouseFocus(){}
	virtual void OnMouseBlur(){}
	virtual void OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle){}
	virtual void OnMouseWheel(bool Up, bool Down){}  //Not implemented
	virtual void OnLButtonDown(int mX, int mY){}
	virtual void OnLButtonUp(int mX, int mY){}
	virtual void OnRButtonDown(int mX, int mY){}
	virtual void OnRButtonUp(int mX, int mY){}
	virtual void OnMButtonDown(int mX, int mY){}
	virtual void OnMButtonUp(int mX, int mY){}
	virtual void OnJoyAxis(Uint8 which, Uint8 axis, Sint16 value){}
	virtual void OnJoyButtonDown(Uint8 which, Uint8 button){}
	virtual void OnJoyButtonUp(Uint8 which, Uint8 button){}
	virtual void OnJoyHat(Uint8 which, Uint8 hat, Uint8 value){}
	virtual void OnJoyBall(Uint8 which, Uint8 ball, Sint16 xrel, Sint16 yrel){}
	virtual void OnMinimize(){}
	virtual void OnRestore(){}
	virtual void OnResize(int w,int h){}
	virtual void OnExpose(){}
	virtual void OnExit(){}
	virtual void OnUser(Uint8 type, int code, void* data1, void* data2){}
};


void EventHandler::OnEvent(SDL_Event* Event) {
    switch(Event->type) {
	case SDL_ACTIVEEVENT: {
		switch(Event->active.state) {
		case SDL_APPMOUSEFOCUS: {
			if ( Event->active.gain )   OnMouseFocus();
			else                OnMouseBlur();

			break;
		}
		case SDL_APPINPUTFOCUS: {
			if ( Event->active.gain )   OnInputFocus();
			else                OnInputBlur();

			break;
		}
		case SDL_APPACTIVE: {
			if ( Event->active.gain )   OnRestore();
			else                OnMinimize();

			break;
		}
		}
		break;
	}

	case SDL_KEYDOWN: {
		OnKeyDown(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
		break;
	}

	case SDL_KEYUP: {
		OnKeyUp(Event->key.keysym.sym,Event->key.keysym.mod,Event->key.keysym.unicode);
		break;
	}

	case SDL_MOUSEMOTION: {
		OnMouseMove(Event->motion.x,Event->motion.y,Event->motion.xrel,Event->motion.yrel,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_LEFT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_RIGHT))!=0,(Event->motion.state&SDL_BUTTON(SDL_BUTTON_MIDDLE))!=0);
		break;
	}

	case SDL_MOUSEBUTTONDOWN: {
		switch(Event->button.button) {
		case SDL_BUTTON_LEFT: {
			OnLButtonDown(Event->button.x,Event->button.y);
			break;
		}
		case SDL_BUTTON_RIGHT: {
			OnRButtonDown(Event->button.x,Event->button.y);
			break;
		}
		case SDL_BUTTON_MIDDLE: {
			OnMButtonDown(Event->button.x,Event->button.y);
			break;
		}
		}
		break;
	}

	case SDL_MOUSEBUTTONUP: {
		switch(Event->button.button) {
		case SDL_BUTTON_LEFT: {
			OnLButtonUp(Event->button.x,Event->button.y);
			break;
		}
		case SDL_BUTTON_RIGHT: {
			OnRButtonUp(Event->button.x,Event->button.y);
			break;
		}
		case SDL_BUTTON_MIDDLE: {
			OnMButtonUp(Event->button.x,Event->button.y);
			break;
		}
		}
		break;
	}

	case SDL_JOYAXISMOTION: {
		OnJoyAxis(Event->jaxis.which,Event->jaxis.axis,Event->jaxis.value);
		break;
	}

	case SDL_JOYBALLMOTION: {
		OnJoyBall(Event->jball.which,Event->jball.ball,Event->jball.xrel,Event->jball.yrel);
		break;
	}

	case SDL_JOYHATMOTION: {
		OnJoyHat(Event->jhat.which,Event->jhat.hat,Event->jhat.value);
		break;
	}
	case SDL_JOYBUTTONDOWN: {
		OnJoyButtonDown(Event->jbutton.which,Event->jbutton.button);
		break;
	}

	case SDL_JOYBUTTONUP: {
		OnJoyButtonUp(Event->jbutton.which,Event->jbutton.button);
		break;
	}

	case SDL_QUIT: {
		OnExit();
		break;
	}

	case SDL_SYSWMEVENT: {
		//Ignore
		break;
	}

	case SDL_VIDEORESIZE: {
		OnResize(Event->resize.w,Event->resize.h);
		break;
	}

	case SDL_VIDEOEXPOSE: {
		OnExpose();
		break;
	}

	default: {
		OnUser(Event->user.type,Event->user.code,Event->user.data1,Event->user.data2);
		break;
	}
    }
}



const unsigned win_num = 3; 



class EventDispatcher : public EventHandler
{
public :
	typedef vector<float, 4> color_type ;
	typedef float value_type ;
	typedef math::HierarchyMesh<math::hierarchy_mesh_traits<value_type, 3> >  mesh_type ;
	typedef math::hierarchy_mesh_traits <value_type, 3>::FaceType            face_type ;

public :
	/**
	 * configure projection and viewport
	 */
	virtual void setViewport(int x, int y, int w, int h)
	{
		// set viewport to be the entire window
		glViewport((GLsizei)x, (GLsizei)y, (GLsizei)w, (GLsizei)h);

		/* Setup our viewport. */
		glMatrixMode( GL_PROJECTION );
		glLoadIdentity( );
		float ratio = (float) w / (float) h;
		gluPerspective( 60.0, ratio, 0.0001f, 100.0f );

		// switch to modelview matrix in order to set scene
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();

	}

	void init_light(void)
	{
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
		glEnable(GL_NORMALIZE);

		// Light model parameters:
		// -------------------------------------------

		GLfloat lmKa[] = {0.0, 0.0, 0.0, 0.0 };
		glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmKa);

		glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, 1.0);
		glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, 0.0);

		// -------------------------------------------
		// Spotlight Attenuation

		GLfloat spot_direction[] = {1.0, -1.0, -1.0 };
		GLint spot_exponent = 30;
		GLint spot_cutoff = 180;

		glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction);
		glLighti(GL_LIGHT0, GL_SPOT_EXPONENT, spot_exponent);
		glLighti(GL_LIGHT0, GL_SPOT_CUTOFF, spot_cutoff);

		GLfloat Kc = 1.0;
		GLfloat Kl = 0.0;
		GLfloat Kq = 0.0;

		glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION,Kc);
		glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, Kl);
		glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, Kq);


		// ------------------------------------------- 
		// Lighting parameters:

		GLfloat light_pos[] = {0.0f, 5.0f, 5.0f, 1.0f};
                
		GLfloat light_Ka[]  = {0.8f, .8f, .8f, 1.0f};
		GLfloat light_Kd[]  = {1.0f, 1.0f, 1.0f, 1.0f};
		GLfloat light_Ks[]  = {.8f, .8f, .8f, 1.0f};

		//GLfloat light_Ka[]  = {1.0f, 0.5f, 0.5f, 1.0f};
		//GLfloat light_Kd[]  = {1.f, 0.1f, 0.1f, 1.0f};
		//GLfloat light_Ks[]  = {1.0f, 1.0f, 1.0f, 1.0f};

		glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
		glLightfv(GL_LIGHT0, GL_AMBIENT, light_Ka);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, light_Kd);
		glLightfv(GL_LIGHT0, GL_SPECULAR, light_Ks);

		// -------------------------------------------
		// Material parameters:

		//GLfloat material_Ka[] = {0.5f, 0.0f, 0.0f, 1.0f};
		//GLfloat material_Kd[] = {0.4f, 0.4f, 0.5f, 1.0f};
		//GLfloat material_Ks[] = {0.8f, 0.8f, 0.0f, 1.0f};
		GLfloat material_Ka[] = {.5f, .5f, .5f, 1.0f};
		GLfloat material_Kd[] = {0.4f, 0.4f, 0.4f, 1.0f};
		GLfloat material_Ks[] = {0.8f, 0.8f, 0.8f, 1.0f};
		GLfloat material_Ke[] = {0.1f, 0.1f, 0.1f, 0.0f};
		GLfloat material_Se = 20.0f;

		glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material_Ka);
		glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material_Kd);
		glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material_Ks);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, material_Ke);
		glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material_Se);

	}


	virtual void init(int _width , int _height )
	{
		std::cout<<"INIT"<<std::endl;

		glShadeModel(GL_SMOOTH);                        // shading mathod: GL_SMOOTH or GL_FLAT
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);          // 4-byte pixel alignment

		// enable /disable features
		glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
		//glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
		//glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_CULL_FACE);
		glEnable(GL_BLEND);
		glEnable(GL_SCISSOR_TEST);
		glClearColor( 0, 0, 0, 0 );
		

		init_light();

		std::cout<<"INIT LIGHT"<<std::endl;
		mp_model->get_texture()->init(); 
		std::cout<<"INIT TEXTURE"<<std::endl;
		mesh_type  * pmesh = mp_model->get_mesh (); 
		for (unsigned i = 0; i < pmesh->faces.size(); ++i)
		{
			face_type * face = pmesh->faces[i] ;
			face->set_texture (mp_model->get_texture() ); 
			face->set_memid(i );
		}

		setViewport(0,0, _width, _height);
		resize(_width, _height);

	}


    // Acutally we need an layout engine for resize. 
	void resize (int w, int h)
	{
		m_win_width = w; 
		m_win_height = h; 

		GLView * pview = NULL ;
		unsigned left, top, width, height; 
		top = h; 
		const unsigned panel_size = 200; 
		for (unsigned i = 0; i < mv_controller.size(); ++i)
		{
			switch (i)
			{
			case 0 :
				mv_controller[0]->get_view() ->set_region((w- 200)/2, h, 0, 0);
				break; 
			case 1:
				mv_controller[1]->get_view() ->set_region((w-200)/2, h, (w-200) /2, 0);
				break; 
			default:
				pview = mv_controller[i]->get_view() ;
				left = w - 200; 

				//mv_controller[i]->get_view() ->set_region(200, h, left, top);
				width = pview->get_width(); 
				height = pview->get_height(); 
				top -= height ;
				pview->set_region(width, height, left, top);

				break; 
			}
			mv_controller[i]->get_view()->resize(w,h);
		}
		for (unsigned i = 0; i < mv_controller.size(); ++i)
		{
			mv_controller[i]->get_view()->set_valid(false );
		}
	}

protected :
	unsigned m_win_width  ;
	unsigned m_win_height ;

	// Select and translate the y win coordinate. 

	bool view_selector (int x, int y, unsigned & id)
	{
		y = m_win_height - y ;
		for (unsigned i = 0; i < mv_controller.size(); ++i)
		{
			if (mv_controller[i]->get_view()->is_inregion(x, y)) 
			{
				id = i; 
			   return true ;
			}
		} 
		return false; 
	} 

public:


EventDispatcher(Model * model) :mp_shader(NULL), Running(true), mp_model (model){}
	~ EventDispatcher () {if (mp_shader) delete mp_shader ;}

	int OnExecute()
	{
		if(OnInit() == false) {
			return -1;
		}
		SDL_Event Event;
		while(Running) {
			while(SDL_PollEvent(&Event)) {
				OnEvent(&Event);
			}

			OnLoop();
			OnRender();
		}
		OnCleanup();
		return 0;
	}

public:

	void onRegister (Controller * p)
	{
		this->mv_controller.push_back (p); 
	}

	bool OnInit(int _width = 900, int _height = 600)
	{
		std::cout<<"OnINIT"<<std::endl;
		/* Information about the current video settings. */
		const SDL_VideoInfo* info = NULL;

		bpp = 0;
		flags = 0;
		if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) 
		{			
			throw(SDL_GetError());
			return false;
		}
		info = SDL_GetVideoInfo( );
		if( !info ) 
		{
			throw("Video query failed: %s\n");				
			return false;
		}

		bpp = info->vfmt->BitsPerPixel;

		SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
		SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
		SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

		flags = SDL_OPENGL | SDL_RESIZABLE; //SDL_FULLSCREEN;

		if( SDL_SetVideoMode( _width, _height, bpp, flags ) == 0 ) {
			/* 
			 * This could happen for a variety of reasons,
			 * including DISPLAY not being set, the specified
			 * resolution not being available, etc.
			 */
			throw("Video mode set failed: ");
			return false;
		}
		init(_width, _height);
		mp_shader = NULL ;

		mp_shader = new Shader; 
		mp_shader->init(); 
		//mp_shader->load("../glsl/texture.vert", "../glsl/texture.frag");
		//mp_shader->load("../glsl/minimal.vert", "../glsl/minimal.frag");
		mp_shader->load("../glsl/pointlight.vert", "../glsl/pointlight.frag");
		mp_shader->bind(); 

		for (unsigned i = 0 ; i < mv_controller.size(); ++i)
		{
			mv_controller[i]->get_view()->set_shader (mp_shader);
		}
		return true;
	}

	void OnEvent(SDL_Event* Event)
	{
		EventHandler::OnEvent(Event);
	}

	void OnLoop()
	{
	}

	void OnRender()
	{
		for (unsigned i = 0; i < mv_controller.size(); ++i)
		{
//			if (mv_controller[i]->get_view()->is_valid() == false)
//			{
				mv_controller[i]->OnDraw(); 
//				mv_controller[i]->get_view()->set_valid(true );
//				break; 
//			}
		}

		SDL_GL_SwapBuffers( );
	}

	void OnCleanup()
	{
		SDL_Quit();
	}

	void OnExit ()
	{
		Running = false ;
	}

	void OnLButtonDown (int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnLButtonDown(mX, mY) ; 
	}

	void OnLButtonUp (int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnLButtonUp(mX, mY) ; 	
	}

	void OnRButtonDown (int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnRButtonDown(mX, mY) ; 
	}

	void OnRButtonUp (int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnRButtonUp(mX, mY) ; 
	}

	void OnMButtonDown(int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnMButtonDown(mX, mY) ; 

	}

	void OnMButtonUp(int mX, int mY)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
			mv_controller[id]->OnMButtonUp(mX, mY) ; 

	}
	void OnMouseMove(int mX, int mY, int relX, int relY, bool Left,bool Right,bool Middle)
	{
		unsigned id ; 
		if (view_selector(mX, mY, id))
		{
			/*
			for (unsigned i = 0; i < mv_controller.size(); ++i)
			{
				mv_controller[i]->get_view()->set_focus(false ) ;
			}
			
			mv_controller[id]->get_view()->set_focus(true ) ;
			*/
			mv_controller[id]->OnMouseMove(mX, mY, relX, relY, Left, Right, Middle) ;
		}
	}

	void OnResize(int w, int h)
	{
		SDL_SetVideoMode(w, h, bpp, flags);

		resize(w,h);
	}

	void OnKeyUp(SDLKey sym, SDLMod mod, Uint16 unicode)
	{
		// short cuts are brocasted to all the windows.
		for (unsigned i = 0; i < mv_controller.size(); ++i)
		{
			mv_controller[i]->OnKeyUp (sym, mod, unicode);
		}
		// we only have two layers. 
		/*
		mesh_type * mesh = mp_model->get_mesh(); 
		MeshView * view = (MeshView *) mp_model->query_control(MESH_VIEW_CTRL);
		if (sym == 'u' || sym == 'U')
		{
			view->up_sampling();
			return ;
		}

		if (sym == 'd' || sym == 'D')
		{
			view->down_sampling(); 
			return ;
		}

		if (sym == 'm' || sym == 'M')
		{

			for (unsigned i = 0; i < mesh->faces.size(); ++i)
			{
				bool flag = mesh->faces[i]->get_merge_all_layers();
				mesh->faces[i]->set_merge_all_layers(!flag);
				MeshView * view = (MeshView *) mp_model->query_control(MESH_VIEW_CTRL);
				view->set_update_whole_model();
			}
			return ;
		}
		unsigned id = unsigned (sym - '0'); // Layer zero is not paintable. 
		if (id >= 3) return ; 
		mp_model->set_layer(id);
		for (unsigned i = 0; i < mesh->faces.size(); ++i)
		{
			mesh->faces[i]->set_layer_id(id );
		}
		std::cout<<id<<std::endl;

		view->set_update_whole_model(); 
		*/
	}
protected:
	bool             Running;
	int bpp ;
	int flags ;
	std::vector <Controller * > mv_controller ;

	Shader * mp_shader; 
	Model * mp_model ; 
};

}}
 
#endif
